#include "geometryBuilder.h"
#include "../core/Logger.h"
#include "geometrybuilderlua.h"
#include "../core/ObjectFactory.h"

RegisterCppObj<H3DGeometryBuilder> g_register_h3dgeobuilder_object;

void H3DGeoStream::push(double val1)
{
	if(packElements != 1)
	{
		LOG(logERROR) << "Cannot push 1 element to a " << packElements << " stream!";
		return;
	}
	values.push_back(val1);
	++idx;
}

void H3DGeoStream::push(double val1, double val2)
{
	if(packElements != 2)
	{
		LOG(logERROR) << "Cannot push 2 elements to a " << packElements << " stream!";
		return;
	}
	values.push_back(val1);
	values.push_back(val2);
	//LOG(logDEBUG) << "Pushing " << val1 << " " << val2;
	++idx;
}

void H3DGeoStream::push(double val1, double val2, double val3)
{
	if(packElements != 3)
	{
		LOG(logERROR) << "Cannot push 3 elements to a " << packElements << " stream!";
		return;
	}
	values.push_back(val1);
	values.push_back(val2);
	values.push_back(val3);
	//LOG(logDEBUG) << "Pushing " << val1 << " " << val2 << " " << val3;
	++idx;
}

void H3DGeoStream::push(double val1, double val2, double val3, double val4)
{
	if(packElements != 4)
	{
		LOG(logERROR) << "Cannot push 4 elements to a " << packElements << " stream!";
		return;
	}
	values.push_back(val1);
	values.push_back(val2);
	values.push_back(val3);
	values.push_back(val4);
	++idx;
}

void H3DGeoStream::fillTo(int fidx, double val)
{
	unsigned int nfill = fidx * packElements;
	while(values.size() < nfill)
		values.push_back(val);
}

void H3DGeoStream::writestream(std::stringstream& ss)
{
	// write our magic header
	ss.write((char*)&streamID, sizeof(int));

	// how big each element is
	int vsize = 0;
	switch(packType)
	{
	case H3DPACK_FLOAT:
		vsize = sizeof(float);
		break;
	case H3DPACK_SHORT:
		vsize = sizeof(short);
		break;
	case H3DPACK_ICHAR:
		vsize = 1;
		break;
	case H3DPACK_FCHAR:
		vsize = 1;
		break;
	}
	int totalsize = vsize * packElements;
	ss.write((char*)&totalsize, sizeof(int));

	// actual elements
	switch(packType)
	{
	case H3DPACK_FLOAT:
		LOG(logDEBUG) << "Writing floats...";
		for (unsigned int i = 0; i < values.size(); ++i)
		{
			float f = (float)(values[i]);
			ss.write((char*) &f, sizeof(float));
		}
		break;
	case H3DPACK_SHORT:
		LOG(logDEBUG) << "Writing shorts...";
		for (unsigned int i = 0; i < values.size(); ++i)
		{
			short f = (short)(values[i] * 32767.0);
			ss.write((char*) &f, sizeof(short));
		}
		break;
	case H3DPACK_ICHAR:
		LOG(logDEBUG) << "Writing ichars...";
		for (unsigned int i = 0; i < values.size(); ++i)
		{
			unsigned char f = (unsigned char)(values[i]);
			ss.write((char*) &f, sizeof(unsigned char));
		}
		break;
	case H3DPACK_FCHAR:
		LOG(logDEBUG) << "Writing fchars...";
		for (unsigned int i = 0; i < values.size(); ++i)
		{
			unsigned char f = (unsigned char)(values[i] * 255.0);
			ss.write((char*) &f, sizeof(unsigned char));
		}
		break;
	default:
		LOG(logERROR) << "Unknown packType " << packType;
	}
}

void H3DGeoStream::clear()
{
	values.clear();
	idx = 0;
}

H3DGeometryBuilder::H3DGeometryBuilder()
{
	clear();
}

// Return the c++ type as a string 
std::string H3DGeometryBuilder::getCppTypeName()
{
	return "H3DGeometryBuilder";
}

// returns a description of this thing
std::string H3DGeometryBuilder::getDescription()
{
	return "Used to build procedural geometry for Horde3d";
}

// create another one of these
AbstractObject* H3DGeometryBuilder::newInstance()
{
	return new H3DGeometryBuilder;
}

// init & update
void H3DGeometryBuilder::init()
{
	// nothing to do
}

void H3DGeometryBuilder::update(double dt)
{
	// nothing to do
}

// push its static functions into the given lua table
void H3DGeometryBuilder::pushStaticFuncs(lua_State* L)
{
	// push superclass static functions...
	AbstractObject::pushStaticFuncs(L);

	// just push these static funcs...
	GeometryBuilderLua::pushStaticFuncs(L);
}

H3DGeometryBuilder::~H3DGeometryBuilder()
{
	// didn't dynamically allocate anything, so...
}

// Interface
void H3DGeometryBuilder::clear()
{
	_indices.clear();
	_meshes.clear();
	_vstart = 0;
	_vused = 0;
	_istart = 0;
	for(int i = 0; i < H3D_GEO_BUILDER_MAX_STREAMS; ++i)
	{
		_streams[i].active = false;
		_streams[i].idx = 0;
		_streams[i].clear();
	}
	setStream(H3DGEOSTREAM_POS, H3DGEOSTREAM_POS, H3DPACK_FLOAT, 3);
	setStream(H3DGEOSTREAM_NORM, H3DGEOSTREAM_NORM, H3DPACK_SHORT, 3);
	setStream(H3DGEOSTREAM_TAN, H3DGEOSTREAM_TAN, H3DPACK_SHORT, 3);
	setStream(H3DGEOSTREAM_BITAN, H3DGEOSTREAM_BITAN, H3DPACK_SHORT, 3);
	setStream(H3DGEOSTREAM_JIND, H3DGEOSTREAM_JIND, H3DPACK_ICHAR, 4);
	setStream(H3DGEOSTREAM_WEIGHTS, H3DGEOSTREAM_WEIGHTS, H3DPACK_FCHAR, 4);
	setStream(H3DGEOSTREAM_TEX0, H3DGEOSTREAM_TEX0, H3DPACK_FLOAT, 2);
	setStream(H3DGEOSTREAM_TEX1, H3DGEOSTREAM_TEX1, H3DPACK_FLOAT, 2);
}

void H3DGeometryBuilder::makeMesh(std::string meshName, H3DRes material)
{
	MeshStub m;
	m.icount = _indices.size() - _istart;
	m.istart = _istart;
	m.vstart = _vstart;
	int maxv = equalizeStreams();
	m.vend = maxv - 1;
	m.name = meshName;
	m.material = material;
	_meshes.push_back(m);
	_istart = _indices.size();
	_vstart = maxv;
}

H3DNode H3DGeometryBuilder::buildModel(H3DNode parent, std::string modelName, std::string geoResName)
{
	// build geo file in memory
	//LOG(logDEBUG) << "BuildModel A...";
	std::stringstream ss;
    const std::string magicHeader = "H3DG";
    const int version = 5;
    const int numJoints = 1;
    const float identityMatrix[] = {   1.0f, 0.0f, 0.0f, 0.0f,  
                        0.0f, 1.0f, 0.0f, 0.0f,  
                        0.0f, 0.0f, 1.0f, 0.0f,  
                        0.0f, 0.0f, 0.0f, 1.0f};
    const int numMorphTargets = 0;
    int numVertexStreams = 0;
	for(int i = 0; i < H3D_GEO_BUILDER_MAX_STREAMS; ++i)
		if(_streams[i].active)
			++numVertexStreams;
	int numVertices = equalizeStreams();
	//LOG(logDEBUG) << "BuildModel B...";

	// header and joints
    ss.write(magicHeader.c_str(), 4*sizeof(char));
    ss.write((char*)&version, sizeof(int));
    ss.write((char*)&numJoints, sizeof(int));
    for (int i = 0; i < 16; ++i)
	{
        ss.write((char*)&(identityMatrix[i]), sizeof(float));
    }

	//LOG(logDEBUG) << "BuildModel C...";

    // num stream and vertices
    ss.write((char*)&numVertexStreams, sizeof(int));
    ss.write((char*)&numVertices, sizeof(int));

	// actual streams
	for(int i = 0; i < H3D_GEO_BUILDER_MAX_STREAMS; ++i)
		if(_streams[i].active)
			_streams[i].writestream(ss);

	//LOG(logDEBUG) << "BuildModel D... " << numVertexStreams;

	// triangle indices
    unsigned int numTriangleIndices = _indices.size();
    ss.write((char*)&numTriangleIndices, sizeof(int));
    for (int i = 0; i < numTriangleIndices; ++i)
	{
        ss.write((char*)&(_indices[i]), sizeof(int));
    }

	//LOG(logDEBUG) << "BuildModel E...";

    ss.write((char*)&numMorphTargets, sizeof(int));
    char* geostream = new char[ss.tellp()];
    ss.read(geostream, ss.tellp());

	//LOG(logDEBUG) << "BuildModel F... " << ss.tellp();

	// geo res
	H3DRes customGeoRes = h3dAddResource(H3DResTypes::Geometry, geoResName.c_str(), 0);
    bool happyloading = h3dLoadResource(customGeoRes, geostream, ss.tellp());
	if(!happyloading)
		LOG(logERROR) << "Error building geometry somehow...";
	//LOG(logDEBUG) << "Got this far in BuildModel...";

	delete[] geostream; // should I be deleting this?

	//LOG(logDEBUG) << "BuildModel G...";

    // model node
	//LOG(logDEBUG) << "Num vertices... " << h3dGetResParamI(customGeoRes, H3DGeoRes::GeometryElem, 0, H3DGeoRes::GeoVertexCountI);
	//LOG(logDEBUG) << "Num indices... " << h3dGetResParamI(customGeoRes, H3DGeoRes::GeometryElem, 0, H3DGeoRes::GeoIndexCountI);
	H3DNode model = h3dAddModelNode(parent, modelName.c_str(), customGeoRes);

	//LOG(logDEBUG) << "BuildModel H...";

    // meshes
	for(unsigned int i = 0; i < _meshes.size(); ++i)
	{
		H3DNode temp = h3dAddMeshNode(model,					// parent
						_meshes[i].name.c_str(),				// name
						_meshes[i].material,					// material
						_meshes[i].istart, _meshes[i].icount,	// batch start, count
						_meshes[i].vstart, _meshes[i].vend);	// vertex start, end
		LOG(logDEBUG) << "Mesh: " << _meshes[i].name << " " << _meshes[i].istart << " " << _meshes[i].icount << " " << _meshes[i].vstart
						<< " " << _meshes[i].vend << " " << _meshes[i].material << " " << temp;
		h3dSetNodeTransform(temp, 0, 0, 0, 0, 0, 0, 1, 1, 1);
	}

	//LOG(logDEBUG) << "BuildModel I...";

	// let's see if this makes any sense...
	float vals[6]; //mix, miy, miz, maxx, maxy, maxz;
	h3dGetNodeAABB(model, &vals[0], &vals[1], &vals[2], &vals[3], &vals[4], &vals[5]);
	//for(int i = 0; i < 6; ++i)
	//	LOG(logDEBUG) << vals[i];

	h3dutDumpMessages();
	clear();
	return model;
}

void H3DGeometryBuilder::makeTriangles()
{
	int maxv = equalizeStreams();
	int numv = maxv - _vused;
	if(numv % 3 != 0)
	{
		LOG(logERROR) << "Trying to make a non-integral number of triangles!" <<
						"(Number of vertices not divisible by three)." <<
						"Got " << numv << " vertices.";
	}
	for(int i = _vused; i + 2 < maxv; i += 3)
	{
		_indices.push_back(i);
		_indices.push_back(i+1);
		_indices.push_back(i+2);
	}
	_vused = maxv;
}

void H3DGeometryBuilder::makeQuads()
{
	int maxv = equalizeStreams();
	int numv = maxv - _vused;
	//LOG(logDEBUG) << "maxv: " << maxv;
	if(numv % 4 != 0)
	{
		LOG(logERROR) << "Trying to make a non-integral number of quads!" <<
						"(Number of vertices not divisible by four)." <<
						"Got " << numv << " vertices.";
	}
	int nm = 0;
	for(int i = _vused; i + 3 < maxv; i += 4)
	{
		// do quad as two triangles
		// 0  3  => 0		0 3
		// 1  2		1 2		  2
		_indices.push_back(i+0);
		_indices.push_back(i+1);
		_indices.push_back(i+2);

		_indices.push_back(i+0);
		_indices.push_back(i+2);
		_indices.push_back(i+3);
		++nm;
	}
	//LOG(logDEBUG) << "Made " << nm << " quads; num indices = " << _indices.size();
	_vused = maxv;

}

void H3DGeometryBuilder::rawIdxTriangle(int idx1, int idx2, int idx3)
{
	_indices.push_back(idx1);
	_indices.push_back(idx2);
	_indices.push_back(idx3);
}

void H3DGeometryBuilder::vposition(double x, double y, double z)
{
	_streams[H3DGEOSTREAM_POS].active = true;
	_streams[H3DGEOSTREAM_POS].push(x, y, z);
}

void H3DGeometryBuilder::vtexcoords(double u, double v)
{
	vtexcoords0(u,v);
}

void H3DGeometryBuilder::vtexcoords0(double u, double v)
{
	_streams[H3DGEOSTREAM_TEX0].active = true;
	_streams[H3DGEOSTREAM_TEX0].push(u, v);
}

void H3DGeometryBuilder::vtexcoords1(double u, double v)
{
	_streams[H3DGEOSTREAM_TEX1].active = true;
	_streams[H3DGEOSTREAM_TEX1].push(u, v);
}

void H3DGeometryBuilder::vnormal(double nx, double ny, double nz)
{
	_streams[H3DGEOSTREAM_NORM].active = true;
	_streams[H3DGEOSTREAM_NORM].push(nx, ny, nz);
}

void H3DGeometryBuilder::vtangent(double tx, double ty, double tz)
{
	_streams[H3DGEOSTREAM_TAN].active = true;
	_streams[H3DGEOSTREAM_TAN].push(tx, ty, tz);
}

void H3DGeometryBuilder::vbitangent(double tx, double ty, double tz)
{
	_streams[H3DGEOSTREAM_BITAN].active = true;
	_streams[H3DGEOSTREAM_BITAN].push(tx, ty, tz);
}

void H3DGeometryBuilder::vweights(double w1, double w2, double w3, double w4)
{
	_streams[H3DGEOSTREAM_WEIGHTS].active = true;
	_streams[H3DGEOSTREAM_WEIGHTS].push(w1, w2, w3, w4);
}

void H3DGeometryBuilder::vjindices(int i1, int i2, int i3, int i4)
{
	_streams[H3DGEOSTREAM_JIND].active = true;
	_streams[H3DGEOSTREAM_JIND].push(i1, i2, i3, i4);
}

int H3DGeometryBuilder::equalizeStreams()
{
	int maxv = 0;
	for(int i = 0; i < H3D_GEO_BUILDER_MAX_STREAMS; ++i)
	{
		if(_streams[i].active)
			maxv = std::max(maxv, _streams[i].idx);
	}
	for(int i = 0; i < H3D_GEO_BUILDER_MAX_STREAMS; ++i)
	{
		if(_streams[i].active)
			_streams[i].fillTo(maxv, 0.0);
	}
	return maxv;
}

void H3DGeometryBuilder::setStream(int sid, int magicId, int packType, int packElements)
{
	_streams[sid].streamID = magicId;
	_streams[sid].packType = packType;
	_streams[sid].packElements = packElements;
}